package cn.sharp.bbs.common;

import cn.sharp.bbs.entity.BO.CurrentUser;
import cn.sharp.bbs.entity.BO.LoginEntity;
import cn.sharp.bbs.service.CommonService;
import cn.sharp.bbs.service.LoginService;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import java.util.*;

public class TokenUtils {

    private static final Logger log = LoggerFactory.getLogger(TokenUtils.class);

    public static final int DEFAULT_EXPIRED_TIME = 10*1000;//毫秒

    public static final int EXPIRED_TIME = getExpiredTime();

    public static final String CLAIM_USERID_NAME = getClaimUseridName();


    private static String getClaimUseridName() {
        String claimUseridName = CommonConfig.getEnv().getProperty("xjtu.token.calim-userid-name");
        if(claimUseridName==null){
            return "UserID";
        }
        return claimUseridName;


    }

    private static int getExpiredTime(){
        String time = CommonConfig.getEnv().getProperty("xjtu.token.expired-time");
        if(time==null){
            return DEFAULT_EXPIRED_TIME;
        }
        try {
            return Integer.parseInt(time);
        } catch (Exception e) {
            e.printStackTrace();
            return DEFAULT_EXPIRED_TIME;
        }
    }

    private static class TokenCache{

        // token 过期时间记录表, key为token值,
        private final LinkedHashMap<String,Long> expiredMap = new LinkedHashMap<>();
        private final Map<String,String> tokenMap = new HashMap<>();

        private TokenCache(){
            Timer timer = new Timer();
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    log.info("开始定期清理失效token...");
                    clearExpiredToken(expiredMap.size()/5+1);
                }
            }, 30000, EXPIRED_TIME/10);
        }

        /**
         * 放入token
         */
        private synchronized void putToken(String key,String token){
            tokenMap.put(key,token);
            expiredMap.put(key,System.currentTimeMillis()+EXPIRED_TIME);
        }

        /**
         * 获取token
         */
        private synchronized String getToken(String key){
            return tokenMap.get(key);
        }

        /**
         * 移除token
         */
        private synchronized void removeToken(String key){
            tokenMap.remove(key);
            expiredMap.remove(key);
        }

        /**
         * 检查并清除过期token
         * @param n: 要检查的token量
         */
        private synchronized void clearExpiredToken(int n){

            int cnt = 0;

            long now = System.currentTimeMillis();
            for(Iterator<Map.Entry<String,Long>> itr = expiredMap.entrySet().iterator();cnt <= n && itr.hasNext();cnt++){
                Map.Entry<String,Long> entry = itr.next();
                if(now > entry.getValue()){
                    String userId = JWT.decode(entry.getKey()).getClaim(CLAIM_USERID_NAME).asString();
                    itr.remove();
                    tokenMap.remove(entry.getKey());
                    log.info("用户"+userId+"token过期, 已下线！");
                }

            }
        }
    }


    private static final TokenCache cache = new TokenCache();


    private static String sign(String userKey,String password){
        Algorithm algorithm = Algorithm.HMAC256(password);
        return JWT.create()
                .withClaim(CLAIM_USERID_NAME,userKey)
                .withExpiresAt(new Date(System.currentTimeMillis()+EXPIRED_TIME/2))
                .sign(algorithm);
    }

    /**
     * 生成一个登录用户签名token
     */
    public static String loginSign(String userKey,String password){
        String token = sign(userKey,password);
        cache.putToken(token,token);
        return token;
    }

    /**
     * 获取用户信息
     */
    public static CurrentUser getUserInfo(String clientToken, CommonService commonService){
        DecodedJWT decodedJWT = JWT.decode(clientToken);
        String userId = decodedJWT.getClaim(CLAIM_USERID_NAME).asString();
        return commonService.getCurrentUser(Long.parseLong(userId));
    }

    public static void removeToken(String clientToken){
        if(clientToken!=null){
            cache.removeToken(clientToken);
        }
    }

    /**
     * 验证token, 在验证失败时抛出异常
     * @param clientToken token值
     * @param loginService 需要使用的工具service
     */
    public static void verifyToken(String clientToken, LoginService loginService){
        if(!StringUtils.hasText(clientToken)){
            //token为空
            throw new RuntimeException("您尚未登录");
        }

        //从客户端登录令牌中获取当前用户账号
        String userId = JWT.decode(clientToken).getClaim(CLAIM_USERID_NAME).asString();
        if(!StringUtils.hasText(userId)){
            //token中账号不存在
            throw new RuntimeException("登录令牌失效！");
        }

        //取出缓存中的登录令牌
        String cacheToken = cache.getToken(clientToken);
        if(!StringUtils.hasText(cacheToken)){
            //缓存中没有登录令牌
            throw new RuntimeException("登录令牌失效！");
        }
        LoginEntity user = loginService.getLoginUserById(Long.parseLong(userId));

        if(user==null){
            throw new RuntimeException("用户不存在！");
        }

        //验证Token有效性
        try{
            Algorithm algorithm = Algorithm.HMAC256(user.getU_pwd());
            JWTVerifier jwtVerifier = JWT.require(algorithm).withClaim(CLAIM_USERID_NAME,userId).build();//构建验证器
            jwtVerifier.verify(cacheToken);
        }catch(TokenExpiredException e){
            //令牌过期，刷新令牌
            String newToken = sign(userId,user.getU_pwd());
            cache.putToken(clientToken,newToken);
            log.info("用户"+userId+"令牌刷新！");
        }catch(Exception e){
            e.printStackTrace();
            //令牌验证未通过
            throw new RuntimeException("令牌错误！请登录。");
        }

    }
}